package maze;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import javax.swing.ImageIcon;

import com.threed.jpct.SimpleVector;
import com.threed.jpct.Texture;

/**
 * A Map
 * @author Team Deer
 * 
 */
public class Map extends BufferedImage {

	// The logic maze, containing information to draw a map
	private final MazeStructure mazeStructure;

	// Width and height of the map
	private final static int mapSize = 512;

	// A cell is this many pixels wide:
	private final float wallSizeX;
	private final float wallSizeY;

	// Background, generated at creation
	private BufferedImage mapBackgroundImage;

	// Mapoverlayradius
	private final int scanningRadius;

	// Arrow representing the player
	private final Image arrow = new ImageIcon(
			"Content\\Sprites\\Arrow\\playerArrow.png").getImage();
	private final Point arrowOrigin = new Point(40, 75);
	private final float arrowSize = 1 / 3f;

	// The position and rotation of player
	private final SimpleVector position;
	private float rotation;

	// Background, generated at creation
	private final BufferedImage playerArrowImage;

	// Constructor
	
	/**
	 * Constructs a Map.
	 */
	public Map(final MazeStructure mazeStructure,
			final SimpleVector startPosition, final float rotation) {
		super(mapSize, mapSize, BufferedImage.TYPE_INT_ARGB);

		this.mazeStructure = mazeStructure;

		this.wallSizeX = (mapSize - 1) / (float) mazeStructure.getWidth();
		this.wallSizeY = (mapSize - 1) / (float) mazeStructure.getHeight();

		this.scanningRadius = mapSize
				/ Math.max(mazeStructure.getWidth(), mazeStructure.getHeight());

		this.createBackground();
		this.playerArrowImage = new BufferedImage(this.getWidth(),
				this.getHeight(), BufferedImage.TYPE_INT_ARGB);

		this.init();

		this.position = new SimpleVector(startPosition);
		this.rotation = rotation;

		this.drawOverlay();
		this.drawMap();
	}

	// Methods
	
	private void init() {
		{
			final Graphics2D g = this.createGraphics();
			g.setColor(new Color(0, 0, 0, 255));
			g.fillRect(0, 0, this.getWidth(), this.getHeight());
		}
		{
			final Graphics2D g = this.playerArrowImage.createGraphics();
			g.setColor(new Color(0, 0, 0, 0));
			g.fillRect(0, 0, this.getWidth(), this.getHeight());
		}
	}

	/**
	 * Sets the players position on the map.
	 * @param position The new position.
	 * @param rotation The new rotation.
	 */
	public void setPlayerState(final SimpleVector position, final float rotation){
		this.position.set(position);
		this.rotation = rotation;
	}
	
	private void createBackground() {
		this.mapBackgroundImage = new BufferedImage(this.getWidth(),
				this.getHeight(), BufferedImage.TYPE_INT_ARGB);
		final Graphics2D mapBackGroundGraphics = this.mapBackgroundImage
				.createGraphics();

		mapBackGroundGraphics.setColor(Color.white);
		mapBackGroundGraphics.fillRect(0, 0, this.getWidth(), this.getHeight());

		mapBackGroundGraphics.setColor(Color.red);

		for (int x = 0; x < this.mazeStructure.getWidth(); x++) {
			for (int y = 0; y < this.mazeStructure.getHeight(); y++) {

				if (this.mazeStructure.cellHasWall(x, y, Wall.EAST)) {
					mapBackGroundGraphics.drawLine(
							(int) ((x + 1) * this.wallSizeY),
							(int) (y * this.wallSizeY),
							(int) ((x + 1) * this.wallSizeY),
							(int) ((y + 1) * this.wallSizeY));
				}

				if (this.mazeStructure.cellHasWall(x, y, Wall.NORTH)) {
					mapBackGroundGraphics.drawLine((int) (x * this.wallSizeX),
							(int) (y * this.wallSizeX),
							(int) ((x + 1) * this.wallSizeX),
							(int) (y * this.wallSizeX));
				}

				if (this.mazeStructure.cellHasWall(x, y, Wall.WEST)) {
					mapBackGroundGraphics.drawLine((int) (x * this.wallSizeY),
							(int) (y * this.wallSizeY),
							(int) (x * this.wallSizeY),
							(int) ((y + 1) * this.wallSizeY));
				}

				if (this.mazeStructure.cellHasWall(x, y, Wall.SOUTH)) {
					mapBackGroundGraphics.drawLine((int) (x * this.wallSizeX),
							(int) ((y + 1) * this.wallSizeX),
							(int) ((x + 1) * this.wallSizeX),
							(int) ((y + 1) * this.wallSizeX));
				}
			}
		}
	}

	/**
	 * Updates the player position.
	 * @param position the position
	 * @param rotationY the rotation
	 */
	public void updatePlayerPosition(final SimpleVector position,
			final float rotationY) {
		this.position.set(position);
		this.rotation = rotationY;
		drawOverlay();
	}

	/**
	 * Draws Map.
	 */
	public void drawMap() {
		final Graphics2D g = this.playerArrowImage.createGraphics();
		final AffineTransform arrowTransformation = new AffineTransform();
		arrowTransformation.scale(this.arrowSize, this.arrowSize);
		arrowTransformation.translate((-this.arrowOrigin.x + .1
				* ((this.position.z - 5) + (5 / this.arrowSize))
				* this.wallSizeX)
				/ this.arrowSize, (-this.arrowOrigin.y + .1
				* (this.position.x + (5 / this.arrowSize)) * this.wallSizeY)
				/ this.arrowSize);
		arrowTransformation.rotate(this.rotation - Math.PI / 2,
				this.arrowOrigin.x, this.arrowOrigin.y);
		g.drawImage(this.arrow, arrowTransformation, null);
	}

	private void drawOverlay() {
		for (int dx = -this.scanningRadius; dx < this.scanningRadius; dx++) {
			for (int dy = -this.scanningRadius; dy < this.scanningRadius; dy++) {

				float opacity = (float) (Math.sqrt(dx * dx + dy * dy) / this.scanningRadius);

				if (opacity > 1f) {
					opacity = 1f;
				}
				opacity = 1 - opacity;

				int x = (int) (this.wallSizeX * (this.position.z + 5) / 10 + dx);
				int y = (int) (this.wallSizeY * (this.position.x + 5) / 10 + dy);
				x = Math.min(Math.max(0, x), this.getWidth() - 1);
				y = Math.min(Math.max(0, y), this.getHeight() - 1);

				final int[] newRGB = this.mapBackgroundImage.getRaster()
						.getPixel(x, y, (int[]) null);
				for (int i = 0; i < newRGB.length; i++) {
					newRGB[i] *= opacity;
				}
				final int[] currentRGB = this.getRaster().getPixel(x, y,
						(int[]) null);

				final int newSum = newRGB[0] + newRGB[1] + newRGB[2];
				final int currentSum = currentRGB[0] + currentRGB[1]
						+ currentRGB[2];

				if (newSum > currentSum) {
					this.getRaster().setPixel(x, y,
							new int[] { newRGB[0], newRGB[1], newRGB[2], 255 });
				}
			}
		}
	}

	/**
	 * 
	 * @return an texture made from this map
	 */
	public Texture getTexture() {
		return new Texture(this, true);
	}

	@Deprecated
	/**
	 * This method is not supported in this version of Map.
	 */
	public Texture getMiniMap() {
		return null;
	}
}